/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
//
// Created by furture on 2017/8/4.
//

#include "wson.h"
#include <stdio.h>


union double_number {
    double d;
    uint64_t l;
    int64_t i64;
};

union float_number {
    float f;
    uint32_t i;
};

#define WSON_BUFFER_SIZE  1024

#define WSON_BUFFER_ENSURE_SIZE(size)  {if((buffer->length) < (buffer->position + (size))){\
                                           msg_buffer_resize(buffer, (uint32_t)(size));\
                                      }}

static inline void msg_buffer_resize(wson_buffer *buffer, uint32_t size) {
    if (size < buffer->length) {
        if (buffer->length < 1024 * 16) {
            size = 1024 * 16;
        } else {
            size = buffer->length;
        }
    } else {
        size += WSON_BUFFER_SIZE;
    }
    size += buffer->length;
    buffer->data = realloc(buffer->data, size);
    buffer->length = size;
}


static inline int32_t
msg_buffer_varint_Zag(uint32_t
ziggedValue) {
int32_t value = (int32_t)
ziggedValue;
return (-(value & 0x01)) ^ ((value >> 1) & ~(1 << 31));
}

static inline uint32_t
msg_buffer_varint_Zig(int32_t
value) {
return (uint32_t) ((value << 1) ^ (value >> 31));

}

wson_buffer *wson_buffer_new(void) {
    wson_buffer * ptr = malloc(sizeof(wson_buffer));
    ptr->data = malloc(sizeof(int8_t) * WSON_BUFFER_SIZE);
    ptr->position = 0;
    ptr->length = WSON_BUFFER_SIZE;
    return ptr;
}

wson_buffer *wson_buffer_from(void *data, uint32_t length) {
    wson_buffer * ptr = malloc(sizeof(wson_buffer));
    ptr->data = data;
    ptr->position = 0;
    ptr->length = length;
    return ptr;
}


inline void wson_buffer_require(wson_buffer *buffer, size_t size) {
    WSON_BUFFER_ENSURE_SIZE(size * sizeof(uint8_t));
}


inline void wson_push_int(wson_buffer *buffer, int32_t value) {
    uint32_t num = msg_buffer_varint_Zig(value);
    wson_push_uint(buffer, num);
}

inline void wson_push_uint(wson_buffer *buffer, uint32_t num) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint32_t) + sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    int size = 0;
    do {
        data[size] = (uint8_t)((num & 0x7F) | 0x80);
        size++;
    } while ((num >>= 7) != 0);
    data[size - 1] &= 0x7F;
    buffer->position += size;
}

inline void wson_push_byte(wson_buffer *buffer, uint8_t bt) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    *data = bt;
    buffer->position += sizeof(uint8_t);
}

inline void wson_push_type(wson_buffer *buffer, uint8_t bt) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    *data = bt;
    buffer->position += sizeof(uint8_t);
}


inline void wson_push_type_boolean(wson_buffer *buffer, uint8_t value) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t) + sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    if (value) {
        *data = WSON_BOOLEAN_TYPE_TRUE;
    } else {
        *data = WSON_BOOLEAN_TYPE_FALSE;
    }
    buffer->position += sizeof(uint8_t);
}


inline void wson_push_type_int(wson_buffer *buffer, int32_t num) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    *data = WSON_NUMBER_INT_TYPE;
    buffer->position += (sizeof(uint8_t));
    wson_push_int(buffer, num);
}

inline void wson_push_type_double(wson_buffer *buffer, double num) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    *data = WSON_NUMBER_DOUBLE_TYPE;
    buffer->position += (sizeof(uint8_t));
    wson_push_double(buffer, num);
}

inline void wson_push_type_float(wson_buffer *buffer, float num) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    *data = WSON_NUMBER_FLOAT_TYPE;
    buffer->position += (sizeof(uint8_t));
    wson_push_float(buffer, num);
}

inline void wson_push_type_long(wson_buffer *buffer, int64_t num) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    *data = WSON_NUMBER_LONG_TYPE;
    buffer->position += (sizeof(uint8_t));
    wson_push_ulong(buffer, num);
}

inline void wson_push_type_string(wson_buffer *buffer, const void *src, int32_t length) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    *data = WSON_STRING_TYPE;
    buffer->position += (sizeof(uint8_t));
    wson_push_uint(buffer, length);
    wson_push_bytes(buffer, src, length);
}

inline void wson_push_type_uint8_string(wson_buffer *buffer, const uint8_t *src, int32_t length) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    *data = WSON_UINT8_STRING_TYPE;
    buffer->position += (sizeof(uint8_t));
    wson_push_uint(buffer, length);
    wson_push_bytes(buffer, src, length);
}

inline void wson_push_property(wson_buffer *buffer, const void *src, int32_t length) {
    wson_push_uint(buffer, length);
    wson_push_bytes(buffer, src, length);
}

inline void wson_push_type_string_length(wson_buffer *buffer, int32_t length) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    *data = WSON_STRING_TYPE;
    buffer->position += (sizeof(uint8_t));
    wson_push_uint(buffer, length);
}

inline void wson_push_type_null(wson_buffer *buffer) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    *data = WSON_NULL_TYPE;
    buffer->position += (sizeof(uint8_t));
}

inline void wson_push_type_map(wson_buffer *buffer, uint32_t size) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    *data = WSON_MAP_TYPE;
    buffer->position += (sizeof(uint8_t));
    wson_push_uint(buffer, size);
}

inline void wson_push_type_array(wson_buffer *buffer, uint32_t size) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    *data = WSON_ARRAY_TYPE;
    buffer->position += (sizeof(uint8_t));
    wson_push_uint(buffer, size);
}


inline void wson_push_type_extend(wson_buffer *buffer, const void *src, int32_t length) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    *data = WSON_EXTEND_TYPE;
    buffer->position += (sizeof(uint8_t));
    wson_push_uint(buffer, length);
    wson_push_bytes(buffer, src, length);
}

inline void wson_push_ensure_size(wson_buffer *buffer, uint32_t dataSize) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint8_t) * dataSize);
}

inline void wson_push_ulong(wson_buffer *buffer, uint64_t num) {
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint64_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    data[7] = (uint8_t)(num & 0xFF);
    data[6] = (uint8_t)((num >> 8) & 0xFF);
    data[5] = (uint8_t)((num >> 16) & 0xFF);
    data[4] = (uint8_t)((num >> 24) & 0xFF);
    data[3] = (uint8_t)((num >> 32) & 0xFF);
    data[2] = (uint8_t)((num >> 40) & 0xFF);
    data[1] = (uint8_t)((num >> 48) & 0xFF);
    data[0] = (uint8_t)((num >> 56) & 0xFF);
    buffer->position += sizeof(uint64_t);
}

void wson_push_double(wson_buffer *buffer, double num) {
    union double_number ld;
    ld.d = num;
    wson_push_ulong(buffer, ld.l);
}

void wson_push_float(wson_buffer *buffer, float f) {
    union float_number fn;
    fn.f = f;
    uint32_t num = fn.i;
    WSON_BUFFER_ENSURE_SIZE(sizeof(uint32_t));
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    data[3] = (uint8_t)(num & 0xFF);
    data[2] = (uint8_t)((num >> 8) & 0xFF);
    data[1] = (uint8_t)((num >> 16) & 0xFF);
    data[0] = (uint8_t)((num >> 24) & 0xFF);
    buffer->position += sizeof(uint32_t);
}


inline void wson_push_bytes(wson_buffer *buffer, const void *src, int32_t length) {
    WSON_BUFFER_ENSURE_SIZE(length);
    void *dst = ((uint8_t *) buffer->data + buffer->position);
    memcpy(dst, src, length);
    buffer->position += length;
}

inline int8_t
wson_next_type(wson_buffer
*buffer) {
int8_t *ptr = (int8_t * )((uint8_t *) buffer->data + buffer->position);
buffer->position += sizeof(int8_t);
return *
ptr;
}

inline int8_t
wson_next_byte(wson_buffer
*buffer) {
int8_t *ptr = (int8_t * )(((uint8_t *) buffer->data + buffer->position));
buffer->position += sizeof(int8_t);
return *
ptr;
}


int32_t wson_next_int(wson_buffer *buffer) {
    return msg_buffer_varint_Zag(wson_next_uint(buffer));
}

uint32_t wson_next_uint(wson_buffer *buffer) {
    uint8_t *ptr = ((uint8_t *) buffer->data + buffer->position);
    uint32_t num = *ptr;
    if ((num & 0x80) == 0) {
        buffer->position += 1;
        return num;
    }
    num &= 0x7F;
    uint8_t chunk = ptr[1];
    num |= (chunk & 0x7F) << 7;
    if ((chunk & 0x80) == 0) {
        buffer->position += 2;
        return num;
    }
    chunk = ptr[2];
    num |= (chunk & 0x7F) << 14;
    if ((chunk & 0x80) == 0) {
        buffer->position += 3;
        return num;
    }

    chunk = ptr[3];
    num |= (chunk & 0x7F) << 21;
    if ((chunk & 0x80) == 0) {
        buffer->position += 4;
        return num;
    }
    chunk = ptr[4];
    num |= (chunk & 0x0F) << 28;
    buffer->position += 5;
    return num;
}

int64_t wson_next_long(wson_buffer *buffer) {
    return wson_next_ulong(buffer);
}

inline uint64_t
wson_next_ulong(wson_buffer
*buffer) {
uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
buffer->position += sizeof(uint64_t);
return (((uint64_t) data[7]) & 0xFF)
+ ((((uint64_t) data[6]) & 0xFF) << 8)
+ ((((uint64_t) data[5]) & 0xFF) << 16)
+ ((((uint64_t) data[4]) & 0xFF) << 24)
+ ((((uint64_t) data[3]) & 0xFF) << 32)
+ ((((uint64_t) data[2]) & 0xFF) << 40)
+ ((((uint64_t) data[1]) & 0xFF) << 48)
+ ((((uint64_t) data[0]) & 0xFF) << 56);
}

double wson_next_double(wson_buffer *buffer) {
    union double_number ld;
    ld.l = wson_next_long(buffer);
    return ld.d;
}

inline float wson_next_float(wson_buffer *buffer) {
    union float_number fn;
    uint8_t *data = ((uint8_t *) buffer->data + buffer->position);
    fn.i = ((data[3]) & 0xFF)
           + (((data[2]) & 0xFF) << 8)
           + (((data[1]) & 0xFF) << 16)
           + (((data[0]) & 0xFF) << 24);
    buffer->position += sizeof(uint32_t);
    return fn.f;
}


inline uint8_t
*
wson_next_bts(wson_buffer
*buffer,
uint32_t length
) {
uint8_t *ptr = ((uint8_t * )
buffer->data + buffer->position);
buffer->position +=
length;
return
ptr;
}

void wson_buffer_free(wson_buffer *buffer) {
    if (buffer->data) {
        free(buffer->data);
        buffer->data = NULL;
    }
    if (buffer) {
        free(buffer);
        buffer = NULL;
    }
}

